
//两个不同的线程将会共用一个 FooBar 实例：
//
//线程 A 将会调用 foo() 方法，而
//线程 B 将会调用 bar() 方法
//
//请设计修改程序，以确保 "foobar" 被输出 n 次。


import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class FooBar {
    private int n;
    //使用阻塞队列来实现线程间的顺序执行,阻塞队列容量为1，当第一任务放入时，在foo中打印出，foo
    /***
     *  take()方法会从队列头拿取元素，如果拿不到时会等待，
     */
    BlockingQueue<Runnable> foo = new LinkedBlockingQueue<>(1);
    BlockingQueue<Runnable> bar = new LinkedBlockingQueue<>(1);
    public FooBar(int n) {
        this.n = n;
    }

    public void foo(Runnable printFoo) throws InterruptedException {
        //第一次放入foo阻塞队列，打印foo,在将任务放入bar阻塞队列
        for (int i = 0; i < n; i++) {
            foo.put(printFoo);
            printFoo.run();
            bar.put(printFoo);
        }
            // printFoo.run() outputs "foo". Do not change or remove this line.

        }


    public void bar(Runnable printBar) throws InterruptedException {
        //等待foo任务执行完毕，会放入bar阻塞队列，此时从bar阻塞队列中取出任务，打印bar，在取出foo的阻塞任务，开始下一轮foo的执行
        for (int i = 0; i < n; i++) {
            bar.take();
            // printBar.run() outputs "bar". Do not change or remove this line.
            printBar.run();
            foo.take();
        }
    }
    //方法二：使用原子性变量来实现线程间的顺序执行
    volatile int flag = 0;
    public void foo2(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            while (flag != 0) {
                Thread.yield();
            }
            printFoo.run();
            flag = 1;
        }
    }
    public void bar2(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            while (flag != 1) {
                Thread.yield();
            }
            printBar.run();
            flag = 0;
        }
    }

    //方法三：使用信号量来实现线程间的顺序执行
    // 在Semaphore的信号量的permits的设置为0或 负数时，表述阻塞 必须先release，再acquire，所以可以控制foo先执行了，才能执行bar3
    private Semaphore fooSemaphore = new Semaphore(1);
    private Semaphore barSemaphore = new Semaphore(0);
    public void foo3(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            fooSemaphore.acquire();
            printFoo.run();
            barSemaphore.release();
        }
    }
    public void bar3(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            barSemaphore.acquire();
            printBar.run();
            fooSemaphore.release();
        }
    }
    //方法四：使用Lock锁来实现线程间的顺序执行,不准确，无法控制两个线程的执行顺序
    private Lock lock = new ReentrantLock();
    public void foo4(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock.lock();
            printFoo.run();
            lock.unlock();
        }
    }
    public void bar4(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock.lock();
            printBar.run();
            lock.unlock();
        }
    }
    //方法五：使用原子性变量和Lock锁来实现线程间的顺序执行
    private volatile int count = 0;
    private Lock lock2 = new ReentrantLock();
    public void foo5(Runnable printFoo) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock2.lock();
            while (count != 0) {
                lock2.unlock();
                Thread.yield();
                lock2.lock();
            }
            printFoo.run();
            count = 1;
            lock2.unlock();
        }
    }
    public void bar5(Runnable printBar) throws InterruptedException {
        for (int i = 0; i < n; i++) {
            lock2.lock();
            while (count != 1) {
                lock2.unlock();
                Thread.yield();
                lock2.lock();
            }
            printBar.run();
            count = 0;
            lock2.unlock();
        }
    }
    public void runTasks4(){
        FooBar fooBar = new FooBar(5);
        Thread fooThread = new Thread(()->{
            try {
                fooBar.foo4(() -> System.out.print("foo"));
                } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            });
        Thread barThread = new Thread(()->{
            try {
                fooBar.bar4(() -> System.out.print("bar"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        fooThread.start();
        barThread.start();
        try {
            fooThread.join();
            barThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    public void runTasks5(){
        FooBar fooBar = new FooBar(5);
        Thread fooThread = new Thread(()->{
            try {
                fooBar.foo5(() -> System.out.print("foo"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread barThread = new Thread(()->{
            try {
                fooBar.bar5(() -> System.out.print("bar"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        fooThread.start();
        barThread.start();
        try {
            fooThread.join();
            barThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    public void runTasks2(){
        FooBar fooBar = new FooBar(5);
        Thread fooThread = new Thread(()->{
            try {
                fooBar.foo2(() -> System.out.print("foo"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread barThread = new Thread(()->{
            try {
                fooBar.bar2(() -> System.out.print("bar"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        fooThread.start();
        barThread.start();
        try {
            fooThread.join();
            barThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public void runTasks3(){
        FooBar fooBar = new FooBar(5);
        Thread fooThread = new Thread(()->{
            try {
                fooBar.foo3(() -> System.out.print("foo"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        Thread barThread = new Thread(()->{
            try {
                fooBar.bar3(() -> System.out.print("bar"));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        fooThread.start();
        barThread.start();
        try {
            fooThread.join();
            barThread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }


    public static void main(String[] args) throws InterruptedException {
//        FooBar fooBar = new FooBar(5);
//        Runnable printFoo = () -> System.out.print("foo");
//        Runnable printBar = () -> System.out.print("bar");
//        Thread fooThread = new Thread(()-> {
//            try {
//                fooBar.foo(printFoo);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//        });
//        Thread barThread = new Thread(() ->{
//            try {
//                fooBar.bar(printBar);
//            } catch (InterruptedException e) {
//                throw new RuntimeException(e);
//            }
//        });
//        fooThread.start();
//        barThread.start();
//        fooThread.join();
//        barThread.join();
        FooBar fooBar = new FooBar(5);
//        fooBar.runTasks4();
        fooBar.runTasks4();
        System.out.println();
        fooBar.runTasks3();
        System.out.println();
        fooBar.runTasks5();

    }
}